from fastapi import APIRouter, Body, Form, Depends, status
from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordRequestForm
from schema.req.user import NewUser as NewUserInReq, UserUpdateInfo
from schema.resp import RestfulModel, RespCode
from schema.resp.user import (
    NewUser as NewUserInResp, Token, DeleteResult, UserInList, UpdateResult as UpdateResultInResp
)
from schema.dao.user import UserInDb, Role
import motor.motor_asyncio
from dependencies import get_mongo_user_coll, require_admin, require_login, get_current_user
from service import user_service
from typing import Optional, List
from datetime import timedelta, datetime
from config import oauth_conf
import uuid
from pymongo.results import UpdateResult as MongoUpdateResult


AsyncIOMotorCollection = motor.motor_asyncio.AsyncIOMotorCollection

user_router = APIRouter(prefix='/user', tags=['用户管理'])

AUTH_FIELD_RESP = JSONResponse(
    status_code=status.HTTP_403_FORBIDDEN,
    content=RestfulModel(code=RespCode.FIELD, message='账号或密码错误', data=None).dict()
)


def _copy_update_result(mongo_update_result: MongoUpdateResult) -> UpdateResultInResp:
    """
    将 mongo 的更新结果拷贝成一个用于返回响应结果的 UpdateResult
    :param mongo_update_result: mongo 的更新结果对象
    :return: 用于返回响应结果的 UpdateResult
    """
    return UpdateResultInResp(
        matched_cnt=mongo_update_result.matched_count,
        modified_cnt=mongo_update_result.modified_count,
    )


@user_router.post('/new',
                  response_model=RestfulModel[Optional[NewUserInResp]],
                  dependencies=[Depends(require_admin), ],
                  summary='创建新用户')
async def create_user(user_info: NewUserInReq = Body(...),
                      coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    new_user = await user_service.create_new_user(coll, user_info.username, user_info.password, user_info.screen_name, user_info.role, user_info.description)
    if new_user is None:
        return RestfulModel(code=RespCode.FIELD, message='用户已存在', data=None)
    await coll.insert_one(new_user.dict())
    new_user_in_resp = NewUserInResp(
        username=new_user.username,
        screen_name=new_user.screen_name,
        role=new_user.role,
        description=new_user.description
    )
    return RestfulModel.success(new_user_in_resp)


@user_router.post('/login',
                  response_model=Token,
                  summary='登录，获取 token',
                  description='账号或密码错误会返回 HTTP 401 错误。\n成功后将获得的 token 放入之后请求头中，示例：`Authorization: Bearer xxxxx`')
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(),
                                 coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    user = await user_service.authenticate_user(coll, form_data.username, form_data.password)
    if not user:
        return AUTH_FIELD_RESP
    access_token_expires = timedelta(days=oauth_conf.ACCESS_TOKEN_EXPIRE_DAYS)
    now = datetime.now()
    jwt_payload = {
        'sub': user.username,
        'iat': now.timestamp(),     # 签发时间
        'jti': str(uuid.uuid1()),   # 编号，用于回避重复攻击
        'exp': (now + access_token_expires).timestamp()     # 过期时间
    }
    access_token = user_service.create_access_token(jwt_payload)
    await user_service.update_user_token(coll, user.username, access_token)
    return Token(code=0, access_token=access_token, token_type='bearer', user_role=user.role)


@user_router.post('/logout',
                  response_model=RestfulModel[str],
                  dependencies=[Depends(require_login), ],
                  summary='登出')
async def logout(username: str = Form(..., title='用户名'),
                 coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    await user_service.update_user_token(coll, username, None)
    return RestfulModel.success('登出成功')


@user_router.delete('/delete',
                    response_model=RestfulModel[DeleteResult],
                    dependencies=[Depends(require_admin), ],
                    summary='删除用户')
async def delete_user(username: str = Form(..., title='所要删除的用户名'),
                      coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    delete_result = await user_service.delete_one_user(coll, username)
    return RestfulModel.success(DeleteResult(delete_count=delete_result.deleted_count))


@user_router.get('/all',
                 response_model=RestfulModel[List[UserInList]],
                 dependencies=[Depends(require_admin), ],
                 summary='查看所有用户')
async def get_all_user(coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    user_list = await user_service.get_all_user(coll)
    return RestfulModel.success(user_list)


@user_router.post('/change-pwd-admin',
                  response_model=RestfulModel[UpdateResultInResp],
                  dependencies=[Depends(require_admin), ],
                  summary='由管理员强制修改某个用户的密码')
async def change_pwd_from_admin(username: str = Body(..., title='所要更换密码的用户的 username'),
                                new_pwd: str = Body(..., title='更换的新密码'),
                                coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    mongo_update_result = await user_service.update_user_pwd(coll, username, new_pwd)
    update_result_in_resp = _copy_update_result(mongo_update_result)
    return RestfulModel.success(update_result_in_resp)


@user_router.post('/change-pwd-self',
                  response_model=RestfulModel[UpdateResultInResp],
                  dependencies=[Depends(require_login), ],
                  summary='某个用户自己修改自己的密码')
async def change_pwd_from_self(old_pwd: str = Body(..., title='原先的旧密码'),
                               new_pwd: str = Body(..., title='更换的新密码'),
                               cur_user: Optional[UserInDb] = Depends(get_current_user),
                               coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    user = await user_service.authenticate_user(coll, cur_user.username, old_pwd)
    if cur_user is None or not user:
        return AUTH_FIELD_RESP
    mongo_update_result = await user_service.update_user_pwd(coll, user.username, new_pwd)
    update_result_in_resp = _copy_update_result(mongo_update_result)
    return RestfulModel.success(update_result_in_resp)


@user_router.post('/change-name',
                  response_model=RestfulModel[UpdateResultInResp],
                  dependencies=[Depends(require_login), ],
                  summary='修改用户的 screen_name')
async def change_screen_name(new_name: str = Body(..., title='新的 screen_name'),
                             cur_user: Optional[UserInDb] = Depends(get_current_user),
                             coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    if cur_user is None:
        return AUTH_FIELD_RESP
    mongo_update_result = await user_service.update_user_screen_name(coll, cur_user.username, new_name)
    update_result_in_resp = _copy_update_result(mongo_update_result)
    return RestfulModel.success(update_result_in_resp)


@user_router.post('/change-role',
                  response_model=RestfulModel[UpdateResultInResp],
                  dependencies=[Depends(require_admin), ],
                  summary='修改用户的 role')
async def change_user_role(username: str = Body(..., title='所要更改角色的用户的username'),
                           new_role: Role = Body(..., title='修改用户的角色'),
                           coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    mongo_update_result = await user_service.update_user_role(coll, username, new_role)
    update_result = _copy_update_result(mongo_update_result)
    return RestfulModel.success(update_result)


@user_router.post('/change-user-info',
                  response_model=RestfulModel[UpdateResultInResp],
                  dependencies=[Depends(require_admin), ],
                  summary='修改用户信息')
async def change_user_info(user_update_info: UserUpdateInfo = Body(...),
                           coll: AsyncIOMotorCollection = Depends(get_mongo_user_coll)):
    mongo_update_result = await user_service.update_user_info(coll, user_update_info)
    update_result = _copy_update_result(mongo_update_result)
    return RestfulModel.success(update_result)
